using System;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.Text;
using System.Reflection;
using System.Runtime.InteropServices;

namespace NBusiness.Utilities
{
    /// <summary>
    /// Summary description for Serialization.
    /// </summary>
    public class Serialization
    {
        /// <summary>
        /// You cannot instantiate this class.
        /// </summary>
        private Serialization() { }

        /// <summary>
        /// Serialize an object graph into a byte []. This will serialize the object, 
        /// all objects referenced by this object
        /// and all objects handling events of this object.
        /// </summary>
        /// <param name="unserialized">The object to be serialized.</param>
        /// <returns>A byte[] representing this object.</returns>
        public static byte[] BinarySerialize(object unserialized)
        {
            try
            {
                AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
                using (MemoryStream ms = new MemoryStream())
                {
                    BinaryFormatter bf = new BinaryFormatter();
                    bf.Serialize(ms, unserialized);
                    return ms.ToArray();
                }
            }
            finally
            {
                AppDomain.CurrentDomain.AssemblyResolve -= new ResolveEventHandler(CurrentDomain_AssemblyResolve);
            }            
        }
        /// <summary>
        /// Deserialize a byte[] into an object and all objects this object referenced when serialized
        /// and all objects handling events of this object. This will NOT restore references but will
        /// effectively create clones of those references.
        /// </summary>
        /// <param name="serialized">The serialized object as a byte[]</param>
        /// <returns>The deserialized object.</returns>
        public static object BinaryDeserialize(byte[] serialized)
        {
            try
            {
                AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
                using (MemoryStream memStream = new MemoryStream(serialized))
                {
                    BinaryFormatter binForm = new BinaryFormatter();
                    return binForm.Deserialize(memStream);
                }
            }
            finally
            {
                AppDomain.CurrentDomain.AssemblyResolve -= new ResolveEventHandler(CurrentDomain_AssemblyResolve);
            }
        }

        static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            Assembly found = null;
            foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())
            {
                if (a.FullName == args.Name)
                {
                    found = a;
                    break;
                }
            }
            return found;
        }
        /// <summary>
        /// Deserialize a byte[] into an object and all objects this object referenced when serialized
        /// and all objects handling events of this object. This will NOT restore references but will
        /// effectively create clones of those references.
        /// </summary>
        /// <typeparam name="T">The type of the object being deserialized.</typeparam>
        /// <param name="serialized">The serialized object as a byte[]</param>
        /// <returns>The deserialized object.</returns>
        public static T BinaryDeserialize<T>(byte[] serialized)
        {
            return (T)BinaryDeserialize(serialized);
        }
        /// <summary>
        /// Serialize an object into XML. This will only serialize public properties with both a get and a set
        /// implemented. All objects must have a public constructor with no arguments. Use the Xml Attributes
        /// to define how your objects are to be Xml Serialized.
        /// </summary>
        /// <param name="serialize">The object to serialize into XML.</param>
        /// <returns>The XML string representation of this object.</returns>
        public static string XMLSerialize(object serialize)
        {
            try
            {
                AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
                using (System.IO.StringWriter sw = new StringWriter())
                {
                    XmlSerializer serializer = new XmlSerializer(serialize.GetType());
                    serializer.Serialize(sw, serialize);
                    return sw.GetStringBuilder().ToString();
                }
            }
            finally
            {
                AppDomain.CurrentDomain.AssemblyResolve -= new ResolveEventHandler(CurrentDomain_AssemblyResolve);
            }
        }
        /// <summary>
        /// Deserialize an XML string into an object of Type T.
        /// </summary>
        /// <typeparam name="T">The type of the object the XML represents.</typeparam>
        /// <param name="deserialize">The XML string representing an object of Type T</param>
        /// <returns>The deserialized object of Type T.</returns>
        public static T XMLDeserialize<T>(string deserialize)
        {
            return (T)XMLDeserialize(deserialize, typeof(T));
        }

        public static object XMLDeserialize(string deserialize, Type t)
        {
            try
            {
                AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
                using (System.IO.StringReader sw = new StringReader(deserialize))
                {
                    XmlSerializer serializer = new XmlSerializer(t);
                    return serializer.Deserialize(sw);
                }
            }
            finally
            {
                AppDomain.CurrentDomain.AssemblyResolve -= new ResolveEventHandler(CurrentDomain_AssemblyResolve);
            }
        }

        /// <summary>
        /// Reads a structure from a stream.  The structure must be such that Marshal.PtrToStructure() can be used.
        /// </summary>
        /// <typeparam name="T">The type of structure to read from the stream.</typeparam>
        /// <param name="stream">The stream to read the structure from.</param>
        /// <returns>The converted object read from the stream.</returns>
        public static T RawReadStructure<T>(Stream stream)
        {
            // allocate array for the raw data
            Type tType = typeof(T);
            int sizeOf = Marshal.SizeOf(tType);
            byte[] rawData = new byte[sizeOf];

            // pin the managed object
            GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned);
            T returnValue = default(T);
            try
            {
                // read the necessary data
                stream.Read(rawData, 0, rawData.Length);

                // get the converted object
                returnValue = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), tType);
            }
            catch
            {
                handle.Free();
                throw;
            }
            finally
            {
                // unpin the managed object
                handle.Free();
            }

            // return the object read from the stream
            return returnValue;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        /// <param name="stream"></param>
        public static void RawWriteStructure<T>(T t, Stream stream)
        {
            // allocate array for the raw data
            Type tType = typeof(T);
            int sizeOf = Marshal.SizeOf(tType);
            byte[] rawData = new byte[sizeOf];

            //GCHandle handle = GCHandle.
            GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned);
            try
            {
                //Set the structure into the byte array
                Marshal.StructureToPtr(t, handle.AddrOfPinnedObject(), true);

                //Write the data to the stream
                stream.Write(rawData, 0, sizeOf);
            }
            catch
            {
                // unpin the managed object
                handle.Free();
                throw;
            }
            finally
            {
                // unpin the managed object
                handle.Free();
            }
        }
    }
}
